<?php

abstract class AbstractRunTimeWorkflowNode
{
  
  /**
   * @var RunTimeWorkflow $workflow
   */
  protected $workflow;
  
  /**
   * @var AbstractDesignTimeWorkflowNode $design_time_node
   */
  protected $design_time_node;
  
  /**
   * @var AbstractRecipientDeterminationRule $recipient
   */
  protected $recipient;
  
  /**
   * @var wfWorkflowWorkItem
   */
  protected $object;
  
  /**
   * @var AbstractWorkflowContainer
   */
  protected $container;

  /**
   * @param RunTimeWorkflow $workflow
   * @param AbstractDesignTimeWorkflowNode $design_time_node
   */
  public function __construct ($workflow, AbstractDesignTimeWorkflowNode $design_time_node = null)
  {
    // TODO Change phpdoc
    if ($workflow instanceof RunTimeWorkflow && ! is_null($design_time_node))
    {
      $this->__constructFromScratch($workflow, $design_time_node);
    }
    elseif ($workflow instanceof wfWorkflowWorkItem)
    {
      $this->__constructFromDatabase($workflow);
    }
    else
    {
      throw new Exception();
    }
    
    $this->container = $this->design_time_node->getMappingIn();
    $this->container->setContainer($this->workflow->getContainer());
  }

  /**
   * @param AbstractWorkflow $workflow
   * @param AbstractDesignTimeWorkflowNode $design_time_node
   */
  protected function __constructFromScratch (RunTimeWorkflow $workflow, AbstractDesignTimeWorkflowNode $design_time_node)
  {
    $this->workflow = $workflow;
    $this->design_time_node = $design_time_node;
  }

  /**
   * @param wfWorkflowWorkItem $item
   */
  protected function __constructFromDatabase (wfWorkflowWorkItem $item)
  {
    $this->object = $item;
    $this->workflow = unserialize($item->getWorkflowInstance()
      ->getSerializedData());
    $this->design_time_node = $this->workflow->getDesignTime()
      ->getNode($item->getStepIdentifier());
  }

  /**
   * @return AbstractDesignTimeWorkflowNode
   */
  public function getDesignTimeNode ()
  {
    return $this->design_time_node;
  }

  /**
   * @return string
   */
  public function getIdentifier ()
  {
    return $this->design_time_node->getIdentifier();
  }

  public final function deliver ()
  {
    if ($this->canBeDelivered())
    {
      $this->doDeliver();
    }
  }

  /**
   * @return bool
   */
  public function canBeDelivered ()
  {
    return true;
  }

  abstract protected function doDeliver ();

  /**
   * @param string $action
   */
  public final function execute ($action = null)
  {
    if ($this->canBeExecuted($action))
    {
      $this->doExecute($action);
    }
  }

  /**
   * @param string $action
   * @return bool
   */
  public function canBeExecuted ($action = null)
  {
    return true;
  }

  abstract protected function doExecute ($action = null);

  public final function complete ()
  {
    if ($this->canBeCompleted())
    {
      $this->doComplete();
    }
  }

  /**
   * @return bool
   */
  public function canBeCompleted ()
  {
    return true;
  }

  abstract protected function doComplete ();

  public function setRecipient (AbstractRecipientDeterminationRule $recipient)
  {
    $this->recipient = $recipient;
  }

  /**
   * @return AbstractWorkflowContainer
   */
  public function getContainer ()
  {
    return $this->container;
  }

  /**
   * @return AbstractRecipientDeterminationRule
   */
  public function getRecipient ()
  {
    if (! is_null($this->recipient))
    {
      return $this->recipient;
    }
    elseif ($this->design_time_node->hasRecipient())
    {
      return $this->design_time_node->getRecipient();
    }
    else
    {
      return new NullRecipientDeterminationRule();
    }
  }

  /**
   * @param WorkflowNodeBindableInterface $bindable
   */
  public function bind (WorkflowNodeBindableInterface $bindable)
  {
    $bindable->bindToNode($this);
  }

  /**
   * Utility method to deliver the first out node found
   */
  protected function deliverFirstOutNode ()
  {
    $out_nodes = $this->design_time_node->getOutNodes();
    $out_node = array_shift($out_nodes);
    $run_time_node = $out_node->createRunTimeNode($this->workflow);
    $run_time_node->deliver();
  }

  /**
   * @return wfWorkflowWorkItem
   */
  public function getObject ()
  {
    return $this->object;
  }

  protected function save ()
  {
    $this->object->setClass(get_called_class());
    $this->object->setStepIdentifier($this->design_time_node->getIdentifier());
    $this->object->setWorkflowInstanceId($this->workflow->getObject()
      ->getId());
    $this->getRecipient()
      ->calculate($this->object);
    $this->object->save();
  }

  /**
   * @return string
   */
  public function getSubject ()
  {
    return sprintf('Work Item #%s', $this->object->getId());
  }

  /**
   * @return string
   */
  public function getMessage ()
  {
    return 'Standard Nachricht, bitte anpassen.';
  }
}